package main

import (
	"bitcask/data"
	"bitcask/index"
	"errors"
	"io"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
)

type DB struct {
	options    Options
	mu         *sync.Mutex
	fileIds    []int                     // 文件id  只能在加载索引的时候使用 不能在其他地方更新和使用
	activeFile *data.DataFile            // 当前活跃文件
	olderFiles map[uint32]*data.DataFile // 旧的数据文件，只能用于读
	index      index.Indexer
}

// Open  打开 bitcask 存储引擎实例
func Open(options Options) (*DB, error) {
	// 对用户传入配置项进行解析校验
	if err := checkOptions(&options); err != nil {
		return nil, err
	}
	// 判断数据目录是否存在，如果不存在的话，则创建这个目录
	if _, err := os.Stat(options.DirPath); os.IsNotExist(err) {
		if err := os.MkdirAll(options.DirPath, os.ModePerm); err != nil {
			return nil, err
		}
	}
	// 初始化 DB 实例结构体
	db := &DB{
		options:    options,
		mu:         new(sync.Mutex),
		olderFiles: make(map[uint32]*data.DataFile),
		index:      index.Indexer(index.NewIndex(options.IndexType)),
	}

}

// Put 写入 key/value  数据  key 不能为空
func (db *DB) Put(key []byte, value []byte) error {
	// 判断key 不能为空
	if len(key) == 0 {
		return ErrKeyIsEmpty
	}

	// 构造 LogRecord 结构体
	log_record := &data.LogRecord{
		Type:  data.LogRecordNormal,
		Key:   key,
		Value: value,
	}

	// 追加写入当前活跃数据文件中
	pos, err := db.appendLogRecord(log_record)
	if err != nil {
		return err
	}

	// 更新内存索引
	if ok := db.index.Put(key, pos); ok != nil {
		return ErrIndexUpdateFailed
	}
	return nil
}

func (db *DB) Delete(key []byte) error {
	// 判断key 的有效性
	if len(key) == 0 {
		return ErrKeyIsEmpty
	}
	// 判断是否存在这个key
	if pos := db.index.Get(key); pos == nil {
		return nil
	}
	// 构造删除记录
	logRecord := &data.LogRecord{
		Type: data.LogRecordDeleted,
		Key:  key,
	}
	_, err := db.appendLogRecord(logRecord)
	if err != nil {
		return err
	}
	// 更新内存索引
	if ok := db.index.Delete(key); !ok {
		return ErrIndexUpdateFailed
	}

	return nil
}

func (db *DB) Get(key []byte) ([]byte, error) {
	//  判断key 的有效性
	if len(key) == 0 {
		return nil, ErrKeyIsEmpty
	}

	// 从内存数据结构中 取出key 对应位置索引信息
	logRecordPos := db.index.Get(key)
	if logRecordPos == nil {
		return nil, ErrKeyNotFound
	}
	var dataFile *data.DataFile
	// 根据文件id 获取对应的数据文件
	if logRecordPos.Fid == db.activeFile.FileId {
		dataFile = db.activeFile
	} else {
		dataFile = db.olderFiles[logRecordPos.Fid]
	}
	// 数据文件为空
	if dataFile == nil {
		return nil, ErrDataFileNotFound
	}

	logRecord, _, err := dataFile.ReadLogRecord(logRecordPos.Offset)
	if err != nil {
		return nil, err
	}
	if logRecord.Type == data.LogRecordDeleted {
		return nil, ErrKeyNotFound
	}
	return logRecord.Value, nil
}

// 追加写数据到活跃文件中
func (db *DB) appendLogRecord(logRecord *data.LogRecord) (*data.LogRecordPos, error) {
	db.mu.Lock()
	defer db.mu.Unlock()
	// 如果为空的话，创建一个活跃文件
	if db.activeFile == nil {
		if err := db.setActiveDataFile(); err != nil {
			return nil, err
		}
	}
	// 写入数据编码
	encRecord, size := data.EncodeLogRecord(logRecord)
	// 如果当前活跃文件已经满了，则创建一个新的活跃文
	if db.activeFile.WriteOff+size > db.options.DataFileSize {
		// 先持久化数据文件，保证已有的数据持久到磁盘
		if err := db.activeFile.Sync(); err != nil {
			return nil, err
		}
		// 当前活跃文件已经满了，创建一个新的活跃文件
		db.olderFiles[db.activeFile.FileId] = db.activeFile
		// 打开新的数据文件
		if err := db.setActiveDataFile(); err != nil {
			return nil, err
		}

	}

	writeOff := db.activeFile.WriteOff
	if err := db.activeFile.Write(encRecord); err != nil {
		return nil, err
	}

	if db.options.SyncWrites {
		if err := db.activeFile.Sync(); err != nil {
			return nil, err
		}
	}

	pos := &data.LogRecordPos{
		Fid:    db.activeFile.FileId,
		Offset: writeOff,
	}
	return pos, nil

}

// 设置活跃文件
// 在访问这个方法，必须持有互斥锁
func (db *DB) setActiveDataFile() error {
	// 创建新的数据文件
	var initalFileId uint32 = 0
	if db.activeFile != nil {
		initalFileId = db.activeFile.FileId + 1
	}
	file, err := data.OpenDataFile(db.options.DirPath, initalFileId)
	if err != nil {
		return err
	}
	db.activeFile = file
	return nil
}

// 检查配置项是否有效
func checkOptions(options *Options) error {
	if options.DirPath == "" {
		return errors.New("database dir path is empty")
	}
	if options.DataFileSize <= 0 {
		return errors.New("database data file size must be greater than 0")
	}
	return nil
}

// 从磁盘中加载数据文件
func (db *DB) loadDataFiles() error {
	// 获取数据目录下所有文件
	dirEntries, err := os.ReadDir(db.options.DirPath)
	if err != nil {
		return err
	}
	var fileIds []int
	// 遍历目录中所有文件 找到所有以.data 后缀结尾的文件
	for _, entry := range dirEntries {
		if strings.HasSuffix(entry.Name(), data.DataFileNameSuffix) {
			splitNames := strings.Split(entry.Name(), ",")
			fileId, err := strconv.Atoi(splitNames[0])
			if err != nil {
				return ErrDataDirrctoryCorrupted
			}
			fileIds = append(fileIds, fileId)
		}
	}

	// 对文件id 进行排序, 从小到大 加载
	sort.Ints(fileIds)
	db.fileIds = fileIds
	// 遍历文件id 打开对应数据文件
	for i, fid := range fileIds {
		dataFile, err := data.OpenDataFile(db.options.DirPath, uint32(fid))
		if err != nil {
			return err
		}
		if i == len(fileIds)-1 {
			db.activeFile = dataFile
		} else {
			db.olderFiles[uint32(fid)] = dataFile
		}

	}
	return nil
}

// 从数据文件中加载索引
// 遍历文件中的所有日志记录，并更新在内存索引中
func (db *DB) loadIndexFromDataFiles() error {
	// 没有文件 说明数据库是空的 直接返回
	if len(db.fileIds) == 0 {
		return nil
	}
	// 遍历所有文件id 处理文件中记录
	for i, fid := range db.fileIds {
		var fileID = uint32(fid)
		var dataFile *data.DataFile
		if fileID == db.activeFile.FileId {
			dataFile = db.activeFile
		} else {
			dataFile = db.olderFiles[fileID]
		}

		var offset int64 = 0
		for {
			logRecord, size, err := dataFile.ReadLogRecord(offset)
			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}

			// 构造内存索引 并保存
			logRecordPos := &data.LogRecordPos{
				Fid:    fileID,
				Offset: offset,
			}
			if logRecord.Type == data.LogRecordDeleted {
				db.index.Delete(logRecord.Key)
			} else {
				db.index.Put(logRecord.Key, logRecordPos)
			}
			// 递增 offset  下次从新的位置开始读取
			offset += size
		}
		// 如果当前是活跃文件 更新这个 文件的 wirteOff
		if i == len(db.fileIds)-1 {
			db.activeFile.WriteOff = offset
		}
	}
	return nil
}
